/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package math;

import org.apache.mahout.math.*;

import java.io.Serializable;

/**
 * sparse matrix with general element values whose rows are accessible quickly. Implemented as a row array of
 * either SequentialAccessSparseVectors or RandomAccessSparseVectors.
 */
public class MySparseRowMatrix extends AbstractMatrix implements Serializable{
    private Vector[] rowVectors;

    private final boolean randomAccessRows;

    /**
     * Construct a sparse matrix starting with the provided row vectors.
     *
     * @param rows              The number of rows in the result
     * @param columns           The number of columns in the result
     * @param rowVectors        a Vector[] array of rows
     */
    public MySparseRowMatrix(int rows, int columns, Vector[] rowVectors) {
        this(rows, columns, rowVectors, false, rowVectors instanceof RandomAccessSparseVector[]);
    }

    public MySparseRowMatrix(int rows, int columns, boolean randomAccess) {
        this(rows, columns, randomAccess
                ? new RandomAccessSparseVector[rows]
                : new SequentialAccessSparseVector[rows],
                true,
                randomAccess);
    }

    public MySparseRowMatrix(int rows, int columns, Vector[] vectors, boolean shallowCopy, boolean randomAccess) {
        super(rows, columns);
        this.randomAccessRows = randomAccess;
        this.rowVectors = vectors.clone();
        for (int row = 0; row < rows; row++) {
            if (vectors[row] == null) {
                // TODO: this can't be right to change the argument
                vectors[row] = randomAccess
                        ? new RandomAccessSparseVector(numCols(), 10)
                        : new SequentialAccessSparseVector(numCols(), 10);
            }
            this.rowVectors[row] = shallowCopy ? vectors[row] : vectors[row].clone();
        }
    }

    /**
     * Construct a matrix of the given cardinality, with rows defaulting to RandomAccessSparseVector implementation
     *
     * @param rows
     * @param columns
     */
    public MySparseRowMatrix(int rows, int columns) {
        this(rows, columns, true);
    }

    @Override
    public Matrix clone() {
        MySparseRowMatrix clone = (MySparseRowMatrix) super.clone();
        clone.rowVectors = new Vector[rowVectors.length];
        for (int i = 0; i < rowVectors.length; i++) {
            clone.rowVectors[i] = rowVectors[i].clone();
        }
        return clone;
    }

    @Override
    public double getQuick(int row, int column) {
        return rowVectors[row] == null ? 0.0 : rowVectors[row].getQuick(column);
    }

    @Override
    public Matrix like() {
        return new SparseRowMatrix(rowSize(), columnSize(), randomAccessRows);
    }

    @Override
    public Matrix like(int rows, int columns) {
        return new SparseRowMatrix(rows, columns, randomAccessRows);
    }

    @Override
    public void setQuick(int row, int column, double value) {
        rowVectors[row].setQuick(column, value);
    }

    @Override
    public int[] getNumNondefaultElements() {
        int[] result = new int[2];
        result[ROW] = rowVectors.length;
        for (int row = 0; row < rowSize(); row++) {
            result[COL] = Math.max(result[COL], rowVectors[row].getNumNondefaultElements());
        }
        return result;
    }

    @Override
    public Matrix viewPart(int[] offset, int[] size) {
        if (offset[ROW] < 0) {
            throw new IndexException(offset[ROW], rowVectors.length);
        }
        if (offset[ROW] + size[ROW] > rowVectors.length) {
            throw new IndexException(offset[ROW] + size[ROW], rowVectors.length);
        }
        if (offset[COL] < 0) {
            throw new IndexException(offset[COL], rowVectors[ROW].size());
        }
        if (offset[COL] + size[COL] > rowVectors[ROW].size()) {
            throw new IndexException(offset[COL] + size[COL], rowVectors[ROW].size());
        }
        return new MatrixView(this, offset, size);
    }

    @Override
    public Matrix assignColumn(int column, Vector other) {
        if (rowSize() != other.size()) {
            throw new CardinalityException(rowSize(), other.size());
        }
        if (column < 0 || column >= columnSize()) {
            throw new IndexException(column, columnSize());
        }
        for (int row = 0; row < rowSize(); row++) {
            rowVectors[row].setQuick(column, other.getQuick(row));
        }
        return this;
    }

    @Override
    public Matrix assignRow(int row, Vector other) {
        if (columnSize() != other.size()) {
            throw new CardinalityException(columnSize(), other.size());
        }
        if (row < 0 || row >= rowSize()) {
            throw new IndexException(row, rowSize());
        }
        rowVectors[row].assign(other);
        return this;
    }

    /**
     *
     * @param row an int row index
     * @return a shallow view of the Vector at specified row (ie you may mutate the original matrix using this row)
     */
    @Override
    public Vector viewRow(int row) {
        if (row < 0 || row >= rowSize()) {
            throw new IndexException(row, rowSize());
        }
        return rowVectors[row];
    }

}
